#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QSettings>
#include <QDirIterator>
#include <QRegExp>
#include "Include/IconProvider.h"
#include "Include/Project.h"
#include "ProjectTreeModel.h"
#include "Node.h"


Node * ProjectTreeModel::createNode(const QFileInfo & info, const QString & spec)
{
    Node * node = NULL;
    if (spec == "designer")
        node = new Node(info, spec, DesignerType);
    if (spec == "resource")
        node = new Node(info, spec, ResourcesType);
    if (info.isDir()){
        if (QDir(info.absoluteFilePath()).entryList(QDir::NoDotAndDotDot | QDir::Files).contains("__init__.py"))
            node = new Node(info, "", PyModuleType);
        else
            node = new Node(info, "", DirectoryType);
    }
    if (info.isFile())
        node = new Node(info, "", FileType);

    if (node){
        node->init();
    }

    return node;
}

Node * ProjectTreeModel::createNode(const QString & info, const QString & spec)
{
    return createNode(QFileInfo(info), spec);
}

ProjectTreeModel::ProjectTreeModel(const QString & path, Plugins::Project *prj, QObject *parent) :
    QAbstractItemModel(parent),
    _watcher(new QFileSystemWatcher(this)),
    _rootPath(path),
    _root(NULL),
    _project(prj),
    _provider(NULL)
{
    connect(_watcher, SIGNAL(directoryChanged(const QString &)), SLOT(onDirectoryChanged(const QString &)));
    connect(_watcher, SIGNAL(fileChanged(QString)), SLOT(onFileChanged(QString)));
    readTree();
}

ProjectTreeModel::~ProjectTreeModel()
{
    delete _watcher;
    delete _root;
}

QModelIndex ProjectTreeModel::index(int row, int column, const QModelIndex & parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    Node *parentNode = parent.isValid() ? static_cast<Node*>(parent.internalPointer()) : _root;
    if (row < parentNode->_children.length()){
        Node *child = parentNode->_children[row];
        if (child)
            return createIndex(row, column, child);
    }
    return QModelIndex();
}

QModelIndex ProjectTreeModel::parent(const QModelIndex & index) const
{
    if (!index.isValid())
        return QModelIndex();
    Node * child = static_cast<Node*>(index.internalPointer());
    if (!child || !child->_parent)
        return QModelIndex();
    return createIndex(child->row(), 0, child->_parent);
}

int ProjectTreeModel::rowCount(const QModelIndex & parent) const
{
    Node *parentNode = parent.isValid() ? static_cast<Node*>(parent.internalPointer()) : _root;
    return parentNode->_children.length();
}

int ProjectTreeModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 1;
}

QVariant ProjectTreeModel::data(const QModelIndex & index, int role) const
{
    if (!index.isValid())
        return QVariant();

    Node *node = static_cast<Node*>(index.internalPointer());
    if (!node)
        return QVariant();

    switch(role){
    case Qt::DisplayRole:{
        if (node->nodeType() == DesignerType)
            return trUtf8("Forms");
        if (node->nodeType() == ResourcesType)
            return trUtf8("Resources");
        return node->name()+(node->isModified() ? "*" : "");
        }
    case Qt::DecorationRole:
        if (_provider){
            switch(node->nodeType()){
            case DirectoryType:
                return _provider->icon(IconProvider::Folder);
            case PyModuleType:
                return _provider->icon(IconProvider::PyModule);
            case FileType:
                return _provider->icon(node->fileInfo());
            case DesignerType:
                return _provider->icon(IconProvider::FormGroup);
            case ResourcesType:
                return _provider->icon(IconProvider::ResourceGroup);
            default:
                return QIcon();
            }
        }
        return QIcon();
    case  Qt::EditRole:
        return node->path();
    case Qt::UserRole+1:
        return node->nodeType();
    case Qt::UserRole+2:
        return node->spec();
    case Qt::UserRole+3:
        return QVariant::fromValue(node);
    }

    return QVariant();
}

QVariant ProjectTreeModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/)
{
    return QVariant();
}

void ProjectTreeModel::readTree(const QString & parentPath, bool rec)
{
    QString path = parentPath;
    if (parentPath.isEmpty()){
        _root = new Node(_rootPath, "", DirectoryType);
        connect(_root, SIGNAL(nodeToDestroy(QList<Node*>)), SLOT(onNodeDestroy(QList<Node*>)));
        _root->init();
        path = _rootPath;
        _watcher->addPath(_rootPath);
    }
    QDir dir(path);

    foreach(QFileInfo item, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs)){
        if (item.fileName() == "__pycache__" || item.fileName().startsWith("."))
            continue;

        Node * node = findNode(item.absoluteFilePath());
        if (!node){
            node = createNode(item);
            connect(node, SIGNAL(nodeToDestroy(QList<Node*>)), SLOT(onNodeDestroy(QList<Node*>)));
            readTree(node->path());
            _watcher->addPath(node->path());
            emit layoutChanged();
        } else if (rec) {
            readTree(node->path());
        }
    }

    dir.setNameFilters(_project->showFiles());
    foreach(QFileInfo item, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)){
        Node *node = findNode(item.absoluteFilePath());
        bool isNew = false;
        if (!node){
            //qDebug() << "create" << item.absoluteFilePath();
            node = createNode(item);
            if (node->baseName() == "__init__"){
                node->_parent->changeType(PyModuleType);
            }
            connect(node, SIGNAL(nodeToDestroy(QList<Node*>)), SLOT(onNodeDestroy(QList<Node*>)));
            isNew = true;
            emit layoutChanged();
        }
        if (isNew){
            if (node->extension() == "ui"){
                node->reparent(specNode(node->_parent->path(), "designer"));
            }
            if (node->extension() == "qrc"){
                node->reparent(specNode(node->_parent->path(), "resource"));
            }
            if (node->name().startsWith("Ui_")){
                QString uiName = node->dirName()+QDir::separator()+node->name().mid(3, node->name().length()-5)+"ui";
                if (Node * parent = findNode(uiName)){
                    node->reparent(parent);
                }
            }
            if (node->name().endsWith("_rc.py")){
                QString rcName = node->dirName()+QDir::separator()+node->name().left(node->name().length()-6)+".qrc";
                if (Node * parent = findNode(rcName)){
                    node->reparent(parent);
                }
            }
            emit layoutChanged();
        }
    }
}

void ProjectTreeModel::onNodeDestroy(QList<Node*> nodes)
{
    emit layoutAboutToBeChanged();
    foreach(Node *n, nodes){
        foreach(QModelIndex ind, find(index(0, 0).parent(), n)){
            beginRemoveRows(ind.parent(), ind.row(), ind.row());
            removeRow(ind.row(), ind.parent());
            endRemoveRows();
        }
    }
    qDeleteAll(nodes);
    emit layoutChanged();
}

Node * ProjectTreeModel::specNode(const QString & path, const QString & type)
{
    QString parentPath = path;
    if (parentPath.indexOf(":"+type) < 0)
        parentPath = path+QDir::separator()+"Special:"+type;

    Node *parent = findNode(parentPath);
    if (!parent){
        return createNode(parentPath, type);
    }
    return parent;
}

void ProjectTreeModel::onDirectoryChanged(const QString & path, bool rec)
{
    //qDebug() << "dir was changed" << path;
    _mutex.lock();

    QString spath = path;
    if (path == "")
        spath = _rootPath;

    Node * node = findNode(spath);
    if (node){
        node->checkExistingChildren(_project->showFiles());
        readTree(spath, rec);
    } else {
        _watcher->removePath(spath);
    }
    emit layoutChanged();
    _mutex.unlock();
}


void ProjectTreeModel::onFileChanged(const QString & file)
{
    qDebug() << "file was changed" << file;
}

void ProjectTreeModel::setIconProvider(IconProvider * provider)
{
    _provider = provider;
}

QList<QModelIndex> ProjectTreeModel::indexForPath(const QString & path)
{
    Node *n = findNode(path);
    QModelIndex startIndex = index(0, 0).parent();
    if (startIndex.internalPointer() == n){
        QList<QModelIndex> list;
        list << startIndex;
        return list;
    }

    return find(startIndex, n);
}

QList<QModelIndex> ProjectTreeModel::find(QModelIndex startIndex, Node * node)
{
    QList<QModelIndex> list;
    for(int i = 0; i < rowCount(startIndex); ++i){
        QModelIndex child = startIndex.child(i, 0);
        if (child.internalPointer() == node)
            list.append(child);
        if (hasChildren(child))
            list.append(find(child, node));
    }
    return list;
}

void ProjectTreeModel::setModify(const QString & path, bool m)
{
    Node * n = findNode(path);
    if (n){
        n->setModified(m);
        foreach(QModelIndex ind, indexForPath(path))
            emit dataChanged(ind, ind);
    }
}
